package com.yangcangclub.commons.velocity;

import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.Template;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.EventHandler;
import org.apache.velocity.app.event.IncludeEventHandler;
import org.apache.velocity.app.event.InvalidReferenceEventHandler;
import org.apache.velocity.app.event.MethodExceptionEventHandler;
import org.apache.velocity.app.event.NullSetEventHandler;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.ParserPool;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.VelocimacroFactory;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.log.Log;
import org.apache.velocity.runtime.log.LogManager;
import org.apache.velocity.runtime.parser.ParseException;
import org.apache.velocity.runtime.parser.Parser;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.apache.velocity.runtime.resource.ContentResource;
import org.apache.velocity.runtime.resource.ResourceManager;
import org.apache.velocity.util.ClassUtils;
import org.apache.velocity.util.RuntimeServicesAware;
import org.apache.velocity.util.StringUtils;
import org.apache.velocity.util.introspection.Introspector;
import org.apache.velocity.util.introspection.Uberspect;
import org.apache.velocity.util.introspection.UberspectLoggable;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;

/**
 * 扩展运期服务类  IF_NULL IF_NOT_NULL
 *
 * @author HumnChou
 */
public class ExtendRuntimeService implements RuntimeConstants, RuntimeServices {
    /**
     * VelocimacroFactory object to manage VMs
     */
    private VelocimacroFactory vmFactory = null;

    /**
     * The Runtime logger. We start with an instance of a 'primordial logger',
     * which just collects log messages then, when the log system is
     * initialized, all the messages get dumpted out of the primordial one into
     * the real one.
     */
    private Log log = new Log();

    /**
     * The Runtime parser pool
     */
    private ParserPool parserPool;

    /**
     * Indicate whether the Runtime is in the midst of initialization.
     */
    private boolean initializing = false;

    /**
     * Indicate whether the Runtime has been fully initialized.
     */
    private boolean initialized = false;

    /**
     * These are the properties that are laid down over top of the default
     * properties when requested.
     */
    private ExtendedProperties overridingProperties = null;

    /**
     * This is a hashtable of initialized directives. The directives that
     * populate this hashtable are taken from the RUNTIME_DEFAULT_DIRECTIVES
     * property file. This hashtable is passed to each parser that is created.
     */
    private Hashtable runtimeDirectives;

    /**
     * Object that houses the configuration options for the velocity runtime.
     * The ExtendedProperties object allows the convenient retrieval of a subset
     * of properties. For example all the properties for a resource loader can
     * be retrieved from the main ExtendedProperties object using something like
     * the following:
     *
     * ExtendedProperties loaderConfiguration = configuration.subset(loaderID);
     *
     * And a configuration is a lot more convenient to deal with then
     * conventional properties objects, or Maps.
     */
    private ExtendedProperties configuration = new ExtendedProperties();

    private ResourceManager resourceManager = null;

    /**
     * This stores the engine-wide set of event handlers. Event handlers for
     * each specific merge are stored in the context.
     */
    private EventCartridge eventCartridge = null;

    /*
     * Each runtime instance has it's own introspector to ensure that each
     * instance is completely separate.
     */
    private Introspector introspector = null;

    /*
     * Opaque reference to something specificed by the application for use in
     * application supplied/specified pluggable components
     */
    private Map applicationAttributes = null;

    private Uberspect uberSpect;

    /*
     * My Directive
     */
    private static final String IF_NULL="Ifnull";
    private static final String IF_NOT_NULL="Ifnotnull";

    /**
     * Creates a new RuntimeInstance object.
     */
    public ExtendRuntimeService(ResourceManager resourceManager) {
        this.resourceManager = resourceManager;
		/*
		 * create a VM factory, introspector, and application attributes
		 */
        vmFactory = new VelocimacroFactory(this);

		/*
		 * make a new introspector and initialize it
		 */
        introspector = new Introspector(getLog());

		/*
		 * and a store for the application attributes
		 */
        applicationAttributes = new HashMap();
    }

    /**
     * This is the primary initialization method in the Velocity Runtime. The
     * systems that are setup/initialized here are as follows:
     *
     * <ul>
     * <li>Logging System</li>
     * <li>ResourceManager</li>
     * <li>EventHandler</li>
     * <li>Parser Pool</li>
     * <li>Global Cache</li>
     * <li>Static Content Include System</li>
     * <li>Velocimacro System</li>
     * </ul>
     *
     * @throws Exception
     *             When an error occured during initialization.
     */
    public synchronized void init() throws Exception {
        if (!initialized && !initializing) {
            initializing = true;
            overridingProperties = new ExtendedProperties();
            overridingProperties.addProperty("userdirective", IF_NULL + "," + IF_NOT_NULL);
            initializeProperties();
            initializeLog();
            // initializeResourceManager();
            initializeDirectives();
            initializeEventHandlers();
            initializeParserPool();
            initializeIntrospection();
			/*
			 * initialize the VM Factory. It will use the properties accessable
			 * from Runtime, so keep this here at the end.
			 */
            // vmFactory.initVelocimacro();
            log.trace("RuntimeInstance successfully initialized.");
            initialized = true;
            initializing = false;
        }
    }

    /**
     * Returns true if the RuntimeInstance has been successfully initialized.
     *
     * @return True if the RuntimeInstance has been successfully initialized.
     */
    public boolean isInitialized() {
        return initialized;
    }

    /**
     * Gets the classname for the Uberspect introspection package and
     * instantiates an instance.
     */
    private void initializeIntrospection() throws Exception {
        String rm = getString(RuntimeConstants.UBERSPECT_CLASSNAME);

        if (rm != null && rm.length() > 0) {
            Object o = null;

            try {
                o = ClassUtils.getNewInstance(rm);
            } catch (ClassNotFoundException cnfe) {
                String err = "The specified class for Uberspect ("
                        + rm
                        + ") does not exist or is not accessible to the current classloader.";
                log.error(err);
                throw new Exception(err);
            }

            if (!(o instanceof Uberspect)) {
                String err = "The specified class for Uberspect (" + rm
                        + ") does not implement " + Uberspect.class.getName()
                        + "; Velocity is not initialized correctly.";

                log.error(err);
                throw new Exception(err);
            }

            uberSpect = (Uberspect) o;

            if (uberSpect instanceof UberspectLoggable) {
                ((UberspectLoggable) uberSpect).setLog(getLog());
            }

            if (uberSpect instanceof RuntimeServicesAware) {
                ((RuntimeServicesAware) uberSpect).setRuntimeServices(this);
            }

            uberSpect.init();
        } else {
			/*
			 * someone screwed up. Lets not fool around...
			 */

            String err = "It appears that no class was specified as the"
                    + " Uberspect.  Please ensure that all configuration"
                    + " information is correct.";

            log.error(err);
            throw new Exception(err);
        }
    }

    /**
     * Initializes the Velocity Runtime with properties file. The properties
     * file may be in the file system proper, or the properties file may be in
     * the classpath.
     */
    private void setDefaultProperties() {
        InputStream inputStream = null;
        try {
            inputStream = getClass().getResourceAsStream(
                    '/' + DEFAULT_RUNTIME_PROPERTIES);

            configuration.load(inputStream);

            if (log.isDebugEnabled()) {
                log.debug("Default Properties File: "
                        + new File(DEFAULT_RUNTIME_PROPERTIES).getPath());
            }

        } catch (IOException ioe) {
            log.error("Cannot get Velocity Runtime default properties!", ioe);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException ioe) {
                log.error("Cannot close Velocity Runtime default properties!",
                        ioe);
            }
        }
    }

    /**
     * Allows an external system to set a property in the Velocity Runtime.
     *
     * @param key
     *            property key
     * @param value
     *            property value
     */
    public void setProperty(String key, Object value) {
        if (overridingProperties == null) {
            overridingProperties = new ExtendedProperties();
        }

        overridingProperties.setProperty(key, value);
    }

    /**
     * Allow an external system to set an ExtendedProperties object to use. This
     * is useful where the external system also uses the ExtendedProperties
     * class and the velocity configuration is a subset of parent application's
     * configuration. This is the case with Turbine.
     *
     * @param configuration
     */
    public void setConfiguration(ExtendedProperties configuration) {
        if (overridingProperties == null) {
            overridingProperties = configuration;
        } else {
            // Avoid possible ConcurrentModificationException
            if (overridingProperties != configuration) {
                overridingProperties.combine(configuration);
            }
        }
    }

    /**
     * Add a property to the configuration. If it already exists then the value
     * stated here will be added to the configuration entry. For example, if
     *
     * resource.loader = file
     *
     * is already present in the configuration and you
     *
     * addProperty("resource.loader", "classpath")
     *
     * Then you will end up with a Vector like the following:
     *
     * ["file", "classpath"]
     *
     * @param key
     * @param value
     */
    public void addProperty(String key, Object value) {
        if (overridingProperties == null) {
            overridingProperties = new ExtendedProperties();
        }

        overridingProperties.addProperty(key, value);
    }

    /**
     * Clear the values pertaining to a particular property.
     *
     * @param key
     *            of property to clear
     */
    public void clearProperty(String key) {
        if (overridingProperties != null) {
            overridingProperties.clearProperty(key);
        }
    }

    /**
     * Allows an external caller to get a property. The calling routine is
     * required to know the type, as this routine will return an Object, as that
     * is what properties can be.
     *
     * @param key
     *            property to return
     * @return Value of the property or null if it does not exist.
     */
    public Object getProperty(String key) {
        Object o = null;

        /**
         * Before initialization, check the user-entered properties first.
         */
        if (!initialized && !initializing && overridingProperties != null) {
            o = overridingProperties.get(key);
        }

        /**
         * After initialization, configuration will hold all properties.
         */
        if (o == null) {
            o = configuration.getProperty(key);
        }
        if (o instanceof String) {
            return StringUtils.nullTrim((String) o);
        } else {
            return o;
        }
    }

    /**
     * Initialize Velocity properties, if the default properties have not been
     * laid down first then do so. Then proceed to process any overriding
     * properties. Laying down the default properties gives a much greater
     * chance of having a working system.
     */
    private void initializeProperties() {
		/*
		 * Always lay down the default properties first as to provide a solid
		 * base.
		 */
        if (configuration.isInitialized() == false) {
            setDefaultProperties();
        }

        if (overridingProperties != null) {
            configuration.combine(overridingProperties);
        }
    }

    /**
     * Initialize the Velocity Runtime with a Properties object.
     *
     * @param p
     * @throws Exception
     *             When an error occurs during initialization.
     */
    public void init(Properties p) throws Exception {
        overridingProperties = ExtendedProperties.convertProperties(p);
        init();
    }

    /**
     * Initialize the Velocity Runtime with the name of ExtendedProperties
     * object.
     *
     * @param configurationFile
     * @throws Exception
     *             When an error occurs during initialization.
     */
    public void init(String configurationFile) throws Exception {
        overridingProperties = new ExtendedProperties(configurationFile);
        init();
    }

    @SuppressWarnings("unused")
    private void initializeResourceManager() throws Exception {
		/*
		 * Which resource manager?
		 */

        String rm = getString(RuntimeConstants.RESOURCE_MANAGER_CLASS);

        if (rm != null && rm.length() > 0) {
			/*
			 * if something was specified, then make one. if that isn't a
			 * ResourceManager, consider this a huge error and throw
			 */

            Object o = null;

            try {
                o = ClassUtils.getNewInstance(rm);
            } catch (ClassNotFoundException cnfe) {
                String err = "The specified class for ResourceManager ("
                        + rm
                        + ") does not exist or is not accessible to the current classloader.";
                log.error(err);
                throw new Exception(err);
            }

            if (!(o instanceof ResourceManager)) {
                String err = "The specified class for ResourceManager (" + rm
                        + ") does not implement "
                        + ResourceManager.class.getName()
                        + "; Velocity is not initialized correctly.";

                log.error(err);
                throw new Exception(err);
            }

            resourceManager = (ResourceManager) o;

            resourceManager.initialize(this);
        } else {
			/*
			 * someone screwed up. Lets not fool around...
			 */

            String err = "It appears that no class was specified as the"
                    + " ResourceManager.  Please ensure that all configuration"
                    + " information is correct.";

            log.error(err);
            throw new Exception(err);
        }
    }

    private void initializeEventHandlers() throws Exception {

        eventCartridge = new EventCartridge();

        /**
         * For each type of event handler, get the class name, instantiate it,
         * and store it.
         */

        String[] referenceinsertion = configuration
                .getStringArray(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION);
        if (referenceinsertion != null) {
            for (int i = 0; i < referenceinsertion.length; i++) {
                EventHandler ev = initializeSpecificEventHandler(
                        referenceinsertion[i],
                        RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION,
                        ReferenceInsertionEventHandler.class);
                if (ev != null)
                    eventCartridge
                            .addReferenceInsertionEventHandler((ReferenceInsertionEventHandler) ev);
            }
        }

        String[] nullset = configuration
                .getStringArray(RuntimeConstants.EVENTHANDLER_NULLSET);
        if (nullset != null) {
            for (int i = 0; i < nullset.length; i++) {
                EventHandler ev = initializeSpecificEventHandler(nullset[i],
                        RuntimeConstants.EVENTHANDLER_NULLSET,
                        NullSetEventHandler.class);
                if (ev != null)
                    eventCartridge
                            .addNullSetEventHandler((NullSetEventHandler) ev);
            }
        }

        String[] methodexception = configuration
                .getStringArray(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION);
        if (methodexception != null) {
            for (int i = 0; i < methodexception.length; i++) {
                EventHandler ev = initializeSpecificEventHandler(
                        methodexception[i],
                        RuntimeConstants.EVENTHANDLER_METHODEXCEPTION,
                        MethodExceptionEventHandler.class);
                if (ev != null)
                    eventCartridge
                            .addMethodExceptionHandler((MethodExceptionEventHandler) ev);
            }
        }

        String[] includeHandler = configuration
                .getStringArray(RuntimeConstants.EVENTHANDLER_INCLUDE);
        if (includeHandler != null) {
            for (int i = 0; i < includeHandler.length; i++) {
                EventHandler ev = initializeSpecificEventHandler(
                        includeHandler[i],
                        RuntimeConstants.EVENTHANDLER_INCLUDE,
                        IncludeEventHandler.class);
                if (ev != null)
                    eventCartridge
                            .addIncludeEventHandler((IncludeEventHandler) ev);
            }
        }

        String[] invalidReferenceSet = configuration
                .getStringArray(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES);
        if (invalidReferenceSet != null) {
            for (int i = 0; i < invalidReferenceSet.length; i++) {
                EventHandler ev = initializeSpecificEventHandler(
                        invalidReferenceSet[i],
                        RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES,
                        InvalidReferenceEventHandler.class);
                if (ev != null) {
                    eventCartridge
                            .addInvalidReferenceEventHandler((InvalidReferenceEventHandler) ev);
                }
            }
        }

    }

    @SuppressWarnings("unchecked")
    private EventHandler initializeSpecificEventHandler(String classname,
                                                        String paramName, Class EventHandlerInterface) throws Exception {
        if (classname != null && classname.length() > 0) {
            Object o = null;
            try {
                o = ClassUtils.getNewInstance(classname);
            } catch (ClassNotFoundException cnfe) {
                String err = "The specified class for "
                        + paramName
                        + " ("
                        + classname
                        + ") does not exist or is not accessible to the current classloader.";
                log.error(err);
                throw new Exception(err);
            }

            if (!EventHandlerInterface.isAssignableFrom(EventHandlerInterface)) {
                String err = "The specified class for " + paramName + " ("
                        + classname + ") does not implement "
                        + EventHandlerInterface.getName()
                        + "; Velocity is not initialized correctly.";

                log.error(err);
                throw new Exception(err);
            }

            EventHandler ev = (EventHandler) o;
            if (ev instanceof RuntimeServicesAware)
                ((RuntimeServicesAware) ev).setRuntimeServices(this);
            return ev;

        } else
            return null;
    }

    /**
     * Initialize the Velocity logging system.
     *
     * @throws Exception
     */
    private void initializeLog() throws Exception {
        // since the Log we started with was just placeholding,
        // let's update it with the real LogChute settings.
        LogManager.updateLog(this.log, this);
    }

    /**
     * This methods initializes all the directives that are used by the Velocity
     * Runtime. The directives to be initialized are listed in the
     * RUNTIME_DEFAULT_DIRECTIVES properties file.
     *
     * @throws Exception
     */
    private void initializeDirectives() throws Exception {
		/*
		 * Initialize the runtime directive table. This will be used for
		 * creating parsers.
		 */
        runtimeDirectives = new Hashtable();

        Properties directiveProperties = new Properties();

		/*
		 * Grab the properties file with the list of directives that we should
		 * initialize.
		 */

        InputStream inputStream = null;

        try {
            inputStream = getClass().getResourceAsStream(
                    '/' + DEFAULT_RUNTIME_DIRECTIVES);

            if (inputStream == null) {
                throw new Exception("Error loading directive.properties! "
                        + "Something is very wrong if these properties "
                        + "aren't being located. Either your Velocity "
                        + "distribution is incomplete or your Velocity "
                        + "jar file is corrupted!");
            }

            directiveProperties.load(inputStream);

        } catch (IOException ioe) {
            log.error("Error while loading directive properties!", ioe);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException ioe) {
                log.error("Cannot close directive properties!", ioe);
            }
        }

		/*
		 * Grab all the values of the properties. These are all class names for
		 * example:
		 *
		 * org.apache.velocity.runtime.directive.Foreach
		 */
        Enumeration directiveClasses = directiveProperties.elements();

        while (directiveClasses.hasMoreElements()) {
            String directiveClass = (String) directiveClasses.nextElement();
            loadDirective(directiveClass);
            log.debug("Loaded System Directive: " + directiveClass);
        }

		/*
		 * now the user's directives
		 */

        String[] userdirective = configuration.getStringArray("userdirective");

        for (int i = 0; i < userdirective.length; i++) {
            loadDirective(userdirective[i]);
            if (log.isInfoEnabled()) {
                log.info("Loaded User Directive: " + userdirective[i]);
            }
        }

    }

    /**
     * instantiates and loads the directive with some basic checks
     *
     * @param directiveClass
     *            classname of directive to load
     */
    @SuppressWarnings("unchecked")
    private void loadDirective(String directiveClass) {
        try {
            Object o = ClassUtils.getNewInstance(directiveClass);

            if (o instanceof Directive) {
                Directive directive = (Directive) o;
                runtimeDirectives.put(directive.getName(), directive);
            } else {
                log.error(directiveClass + " does not implement "
                        + Directive.class.getName() + "; it cannot be loaded.");
            }
        }
        // The ugly threesome: ClassNotFoundException,
        // IllegalAccessException, InstantiationException.
        // Ignore Findbugs complaint for now.
        catch (Exception e) {
            log.error("Failed to load Directive: " + directiveClass, e);
        }
    }

    /**
     * Initializes the Velocity parser pool.
     */
    private void initializeParserPool() throws Exception {
		/*
		 * Which parser pool?
		 */
        String pp = getString(RuntimeConstants.PARSER_POOL_CLASS);

        if (pp != null && pp.length() > 0) {
			/*
			 * if something was specified, then make one. if that isn't a
			 * ParserPool, consider this a huge error and throw
			 */

            Object o = null;

            try {
                o = ClassUtils.getNewInstance(pp);
            } catch (ClassNotFoundException cnfe) {
                String err = "The specified class for ParserPool ("
                        + pp
                        + ") does not exist (or is not accessible to the current classloader.";
                log.error(err);
                throw new Exception(err);
            }

            if (!(o instanceof ParserPool)) {
                String err = "The specified class for ParserPool (" + pp
                        + ") does not implement " + ParserPool.class
                        + " Velocity not initialized correctly.";

                log.error(err);
                throw new Exception(err);
            }

            parserPool = (ParserPool) o;

            parserPool.initialize(this);
        } else {
			/*
			 * someone screwed up. Lets not fool around...
			 */

            String err = "It appears that no class was specified as the"
                    + " ParserPool.  Please ensure that all configuration"
                    + " information is correct.";

            log.error(err);
            throw new Exception(err);
        }

    }

    /**
     * Returns a JavaCC generated Parser.
     *
     * @return Parser javacc generated parser
     */
    public Parser createNewParser() {
		/* must be initialized before we use runtimeDirectives */
        if (!initialized && !initializing) {
            log.debug("Velocity was not initialized! Calling init()...");
            try {
                init();
            } catch (Exception e) {
                getLog().error("Could not auto-initialize Velocity", e);
                throw new IllegalStateException(
                        "Velocity could not be initialized!");
            }
        }

        Parser parser = new Parser(this);
        parser.setDirectives(runtimeDirectives);
        return parser;
    }

    /**
     * Parse the input and return the root of AST node structure. <br>
     * <br>
     * In the event that it runs out of parsers in the pool, it will create and
     * let them be GC'd dynamically, logging that it has to do that. This is
     * considered an exceptional condition. It is expected that the user will
     * set the PARSER_POOL_SIZE property appropriately for their application. We
     * will revisit this.
     *
     * @param reader
     *            Reader retrieved by a resource loader
     * @param templateName
     *            name of the template being parsed
     * @return A root node representing the template as an AST tree.
     * @throws ParseException
     *             When the template could not be parsed.
     */
    public SimpleNode parse(Reader reader, String templateName)
            throws ParseException {
		/*
		 * do it and dump the VM namespace for this template
		 */
        return parse(reader, templateName, true);
    }

    /**
     * Parse the input and return the root of the AST node structure.
     *
     * @param reader
     *            Reader retrieved by a resource loader
     * @param templateName
     *            name of the template being parsed
     * @param dumpNamespace
     *            flag to dump the Velocimacro namespace for this template
     * @return A root node representing the template as an AST tree.
     * @throws ParseException
     *             When the template could not be parsed.
     */
    public SimpleNode parse(Reader reader, String templateName,
                            boolean dumpNamespace) throws ParseException {
		/* must be initialized before using parserPool */
        if (!initialized && !initializing) {
            log.debug("Velocity was not initialized! Calling init()...");
            try {
                init();
            } catch (Exception e) {
                getLog().error("Could not auto-initialize Velocity", e);
                throw new IllegalStateException(
                        "Velocity could not be initialized!");
            }
        }

        SimpleNode ast = null;
        Parser parser = (Parser) parserPool.get();

        if (parser == null) {
			/*
			 * if we couldn't get a parser from the pool make one and log it.
			 */

            if (log.isInfoEnabled()) {
                log.info("Runtime : ran out of parsers. Creating a new one. "
                        + " Please increment the parser.pool.size property."
                        + " The current value is too small.");
            }

            parser = createNewParser();

        }

		/*
		 * now, if we have a parser
		 */

        if (parser != null) {
            try {
				/*
				 * dump namespace if we are told to. Generally, you want to do
				 * this - you don't in special circumstances, such as when a VM
				 * is getting init()-ed & parsed
				 */

                if (dumpNamespace) {
                    dumpVMNamespace(templateName);
                }

                ast = parser.parse(reader, templateName);
            } finally {
				/*
				 * put it back
				 */
                parserPool.put(parser);

            }
        } else {
            log
                    .error("Runtime : ran out of parsers and unable to create more.");
        }
        return ast;
    }

    /**
     * Returns a <code>Template</code> from the resource manager. This method
     * assumes that the character encoding of the template is set by the
     * <code>input.encoding</code> property. The default is "ISO-8859-1"
     *
     * @param name
     *            The file name of the desired template.
     * @return The template.
     * @throws ResourceNotFoundException
     *             if template not found from any available source.
     * @throws ParseErrorException
     *             if template cannot be parsed due to syntax (or other) error.
     * @throws Exception
     *             if an error occurs in template initialization
     */
    public Template getTemplate(String name) throws ResourceNotFoundException,
            ParseErrorException, Exception {
        return getTemplate(name, getString(INPUT_ENCODING, ENCODING_DEFAULT));
    }

    /**
     * Returns a <code>Template</code> from the resource manager
     *
     * @param name
     *            The name of the desired template.
     * @param encoding
     *            Character encoding of the template
     * @return The template.
     * @throws ResourceNotFoundException
     *             if template not found from any available source.
     * @throws ParseErrorException
     *             if template cannot be parsed due to syntax (or other) error.
     * @throws Exception
     *             if an error occurs in template initialization
     */
    public Template getTemplate(String name, String encoding)
            throws ResourceNotFoundException, ParseErrorException, Exception {
		/* must be initialized before using resourceManager */
        if (!initialized && !initializing) {
            log.info("Velocity not initialized yet. Calling init()...");
            init();
        }

        return (Template) resourceManager.getResource(name,
                ResourceManager.RESOURCE_TEMPLATE, encoding);
    }

    /**
     * Returns a static content resource from the resource manager. Uses the
     * current value if INPUT_ENCODING as the character encoding.
     *
     * @param name
     *            Name of content resource to get
     * @return parsed ContentResource object ready for use
     * @throws ResourceNotFoundException
     *             if template not found from any available source.
     * @throws ParseErrorException
     *             When the template could not be parsed.
     * @throws Exception
     *             Any other error.
     */
    public ContentResource getContent(String name)
            throws ResourceNotFoundException, ParseErrorException, Exception {
		/*
		 * the encoding is irrelvant as we don't do any converstion the
		 * bytestream should be dumped to the output stream
		 */

        return getContent(name, getString(INPUT_ENCODING, ENCODING_DEFAULT));
    }

    /**
     * Returns a static content resource from the resource manager.
     *
     * @param name
     *            Name of content resource to get
     * @param encoding
     *            Character encoding to use
     * @return parsed ContentResource object ready for use
     * @throws ResourceNotFoundException
     *             if template not found from any available source.
     * @throws ParseErrorException
     *             When the template could not be parsed.
     * @throws Exception
     *             Any other error.
     */
    public ContentResource getContent(String name, String encoding)
            throws ResourceNotFoundException, ParseErrorException, Exception {
		/* must be initialized before using resourceManager */
        if (!initialized && !initializing) {
            log.info("Velocity not initialized yet. Calling init()...");
            init();
        }

        return (ContentResource) resourceManager.getResource(name,
                ResourceManager.RESOURCE_CONTENT, encoding);
    }

    /**
     * Determines if a template exists and returns name of the loader that
     * provides it. This is a slightly less hokey way to support the
     * Velocity.resourceExists() utility method, which was broken when
     * per-template encoding was introduced. We can revisit this.
     *
     * @param resourceName
     *            Name of template or content resource
     * @return class name of loader than can provide it
     */
    public String getLoaderNameForResource(String resourceName) {
		/* must be initialized before using resourceManager */
        if (!initialized && !initializing) {
            log.debug("Velocity was not initialized! Calling init()...");
            try {
                init();
            } catch (Exception e) {
                getLog().error("Could not initialize Velocity", e);
                throw new IllegalStateException(
                        "Velocity could not be initialized!");
            }
        }

        return resourceManager.getLoaderNameForResource(resourceName);
    }

    /**
     * Returns a convenient Log instance that wraps the current LogChute. Use
     * this to log error messages. It has the usual methods.
     *
     * @return A convenience Log instance that wraps the current LogChute.
     */
    public Log getLog() {
        return log;
    }

    /**
     * @deprecated Use getLog() and call warn() on it.
     * @see Log#warn(Object)
     * @param message
     *            The message to log.
     */
    public void warn(Object message) {
        getLog().warn(message);
    }

    /**
     * @deprecated Use getLog() and call info() on it.
     * @see Log#info(Object)
     * @param message
     *            The message to log.
     */
    public void info(Object message) {
        getLog().info(message);
    }

    /**
     * @deprecated Use getLog() and call error() on it.
     * @see Log#error(Object)
     * @param message
     *            The message to log.
     */
    public void error(Object message) {
        getLog().error(message);
    }

    /**
     * @deprecated Use getLog() and call debug() on it.
     * @see Log#debug(Object)
     * @param message
     *            The message to log.
     */
    public void debug(Object message) {
        getLog().debug(message);
    }

    /**
     * String property accessor method with default to hide the configuration
     * implementation.
     *
     * @param key
     *            property key
     * @param defaultValue
     *            default value to return if key not found in resource manager.
     * @return value of key or default
     */
    public String getString(String key, String defaultValue) {
        return configuration.getString(key, defaultValue);
    }

    /**
     * Returns the appropriate VelocimacroProxy object if strVMname is a valid
     * current Velocimacro.
     *
     * @param vmName
     *            Name of velocimacro requested
     * @param templateName
     *            Name of the template that contains the velocimacro.
     * @return The requested VelocimacroProxy.
     */
    public Directive getVelocimacro(String vmName, String templateName) {
        return vmFactory.getVelocimacro(vmName, templateName);
    }

    /**
     * Adds a new Velocimacro. Usually called by Macro only while parsing.
     *
     * @param name
     *            Name of velocimacro
     * @param macro
     *            String form of macro body
     * @param argArray
     *            Array of strings, containing the #macro() arguments. the 0th
     *            is the name.
     * @param sourceTemplate
     *            Name of the template that contains the velocimacro.
     * @return True if added, false if rejected for some reason (either
     *         parameters or permission settings)
     */
    public boolean addVelocimacro(String name, String macro, String argArray[],
                                  String sourceTemplate) {
        return vmFactory.addVelocimacro(name, macro, argArray, sourceTemplate);
    }

    /**
     * Checks to see if a VM exists
     *
     * @param vmName
     *            Name of the Velocimacro.
     * @param templateName
     *            Template on which to look for the Macro.
     * @return True if VM by that name exists, false if not
     */
    public boolean isVelocimacro(String vmName, String templateName) {
        return vmFactory.isVelocimacro(vmName, templateName);
    }

    /**
     * tells the vmFactory to dump the specified namespace. This is to support
     * clearing the VM list when in inline-VM-local-scope mode
     *
     * @param namespace
     *            Namespace to dump.
     * @return True if namespace was dumped successfully.
     */
    public boolean dumpVMNamespace(String namespace) {
        return vmFactory.dumpVMNamespace(namespace);
    }

	/*
	 * -------------------------------------------------------------------- R U
	 * N T I M E A C C E S S O R M E T H O D S
	 * --------------------------------------------------------------------
	 * These are the getXXX() methods that are a simple wrapper around the
	 * configuration object. This is an attempt to make a the Velocity Runtime
	 * the single access point for all things Velocity, and allow the Runtime to
	 * adhere as closely as possible the the Mediator pattern which is the
	 * ultimate goal.
	 * --------------------------------------------------------------------
	 */

    /**
     * String property accessor method to hide the configuration implementation
     *
     * @param key
     *            property key
     * @return value of key or null
     */
    public String getString(String key) {
        return StringUtils.nullTrim(configuration.getString(key));
    }

    /**
     * Int property accessor method to hide the configuration implementation.
     *
     * @param key
     *            Property key
     * @return value
     */
    public int getInt(String key) {
        return configuration.getInt(key);
    }

    /**
     * Int property accessor method to hide the configuration implementation.
     *
     * @param key
     *            property key
     * @param defaultValue
     *            The default value.
     * @return value
     */
    public int getInt(String key, int defaultValue) {
        return configuration.getInt(key, defaultValue);
    }

    /**
     * Boolean property accessor method to hide the configuration
     * implementation.
     *
     * @param key
     *            property key
     * @param def
     *            The default value if property not found.
     * @return value of key or default value
     */
    public boolean getBoolean(String key, boolean def) {
        return configuration.getBoolean(key, def);
    }

    /**
     * Return the velocity runtime configuration object.
     *
     * @return Configuration object which houses the Velocity runtime
     *         properties.
     */
    public ExtendedProperties getConfiguration() {
        return configuration;
    }

    /**
     * Return the Introspector for this instance
     *
     * @return The Introspector for this instance
     */
    public Introspector getIntrospector() {
        return introspector;
    }

    /**
     * Returns the event handlers for the application.
     *
     * @return The event handlers for the application.
     */
    public EventCartridge getApplicationEventCartridge() {
        return eventCartridge;
    }

    /**
     * Gets the application attribute for the given key
     *
     * @param key
     * @return The application attribute for the given key.
     */
    public Object getApplicationAttribute(Object key) {
        return applicationAttributes.get(key);
    }

    /**
     * Sets the application attribute for the given key
     *
     * @param key
     * @param o
     *            The new application attribute.
     * @return The old value of this attribute or null if it hasn't been set
     *         before.
     */
    @SuppressWarnings("unchecked")
    public Object setApplicationAttribute(Object key, Object o) {
        return applicationAttributes.put(key, o);
    }

    /**
     * Returns the Uberspect object for this Instance.
     *
     * @return The Uberspect object for this Instance.
     */
    public Uberspect getUberspect() {
        return uberSpect;
    }
}
